package schemax

import (
	"gitee.com/zhongguo168a/gocodes/datax"
	"gitee.com/zhongguo168a/gocodes/datax/mapx"
	"gitee.com/zhongguo168a/gocodes/myx/errorx"
	"strconv"
)

type VisitorContext struct {
	// 0-class
	// 1-array/map
	// 2-field
	currentPos int
	// pos=all
	CurrentType IType
	// pos=class/field
	CurrentField *Field
	// field name / array index / map key
	CurrentKey          string
	CurrentSourceParent datax.M
	CurrentSource       interface{}
	CurrentTargetParent datax.M
	CurrentTarget       interface{}
	CurrentDecl         IDecl
}

// 设置当前字段的值
func (visitor *VisitorContext) SetFieldValue(val interface{}) {
	visitor.CurrentTargetParent[visitor.CurrentKey] = val
	visitor.CurrentTarget = val
}

// 中断访问
func (visitor *VisitorContext) Interrupt() {
}

// 中断当前项的访问
func (visitor *VisitorContext) InterruptItem() {
}

func NewVisitor(decl *ClassDecl, source datax.M) (obj *Visitor) {
	obj = &Visitor{
		source: source,
		decl:   decl,
	}
	return
}

type Visitor struct {
	decl    *ClassDecl
	source  datax.M
	target  datax.M
	handler func(ctx *VisitorContext)

	hasTarget bool
	ctx       *VisitorContext
}

func (visitor *Visitor) SetTarget(target datax.M) {
	visitor.target = target
	visitor.hasTarget = true
}

func (visitor *Visitor) SetHandler(f func(visitor *VisitorContext) ()) {
	visitor.handler = f
}

func (visitor *Visitor) Walk() {
	if visitor.source == nil {
		return
	}
	if visitor.hasTarget {
		if visitor.target == nil {
			visitor.target = datax.M{}
		}
	}
	visitor.walkObject(visitor.source, visitor.target, visitor.decl)
}

// todo: 不完整
func (visitor *Visitor) walkObject(obj, targetObj datax.M, decl *ClassDecl) error {
	ctx := visitor.ctx
	for _, field := range decl.GetAllField() {
		fname := field.Alias()
		iVal, has := obj[fname]
		if !has && iVal == nil {
			continue
		}
		ctx.currentPos = 0
		ctx.CurrentSourceParent = obj
		ctx.CurrentSource = iVal
		ctx.CurrentType = field.Type
		ctx.CurrentField = field
		ctx.CurrentKey = fname
		ctx.CurrentDecl = decl
		var iTargetValue interface{}
		if visitor.hasTarget {
			iTargetValue = targetObj[fname]
			if visitor.needCreateTarget(field.Type) {
				if iTargetValue == nil {
					iTargetValue = datax.M{}
					targetObj[fname] = iTargetValue
				}
			}
			ctx.CurrentTargetParent = targetObj
			ctx.CurrentTarget = iTargetValue
		}
		switch ftyp := field.Type.(type) {
		case *ClassType:
			err := visitor.walkClassType(iVal, iTargetValue, ftyp)
			if err != nil {
				return errorx.Wrap(err, "walk class", datax.M{"name": fname})
			}
		case *ArrayType:
			err := visitor.walkArrayType(iVal, iTargetValue, ftyp)
			if err != nil {
				return errorx.Wrap(err, "walk array", datax.M{"name": fname})
			}
		case *MapType:
			err := visitor.walkMapType(iVal, iTargetValue, ftyp)
			if err != nil {
				return errorx.Wrap(err, "walk map", datax.M{"name": fname})
			}
		case *EnumType:
			err := visitor.walkEnumType(iVal, iTargetValue, ftyp)
			if err != nil {
				return errorx.Wrap(err, "walk enum", datax.M{"name": fname})
			}
		case *AnyType:
			err := visitor.walkAnyType(iVal, iTargetValue, ftyp)
			if err != nil {
				return errorx.Wrap(err, "walk any", datax.M{"name": fname})
			}
		default:
			visitor.walkBasicType()
		}

	}

	return nil
}

func (visitor *Visitor) walkClassType(ival, iTarget interface{}, ftyp *ClassType) error {
	mval, ok := ival.(map[string]interface{})
	if ok == false {
		if ival == nil {
			return nil
		}
		return errorx.New("value not map[string]interface{}")
	}

	typDecl := GetDeclByKey(ftyp.Decl)
	if typDecl == nil {
		return errorx.New("decl not found", datax.M{"class": ftyp.Decl})
	}

	return visitor.walkObject(mval, iTarget.(datax.M), typDecl.(*ClassDecl))
}
func (visitor *Visitor) walkArrayType(iObj, iTargetObj interface{}, ftyp *ArrayType) error {
	if iObj == nil {
		return nil
	}

	var mval map[string]interface{}
	switch val := iObj.(type) {
	case map[string]interface{}:
		mval = val
	case []interface{}:
		mval = map[string]interface{}{}
		for i, v := range val {
			mval[strconv.Itoa(i)] = v
		}
	default:
		if iObj == nil {
			return nil
		}
		return errorx.New("value not map[string]interface{}")
	}

	var targetObj map[string]interface{}
	if visitor.hasTarget {
		targetObj = iTargetObj.(map[string]interface{})
	}

	ctx := visitor.ctx
	for key, iVal := range mval {
		ctx.CurrentSourceParent = mval
		ctx.CurrentSource = iVal
		ctx.CurrentType = ftyp
		ctx.CurrentKey = key
		var iTargetValue interface{}
		if visitor.hasTarget {
			iTargetValue = targetObj[key]
			if visitor.needCreateTarget(ftyp.Elem) {
				if iTargetValue == nil {
					iTargetValue = datax.M{}
					targetObj[key] = iTargetValue
				}
			}
			ctx.CurrentTargetParent = targetObj
			ctx.CurrentTarget = iTargetValue
		}

		switch etyp := ftyp.Elem.(type) {
		case *ClassType:
			err := visitor.walkClassType(iVal, iTargetValue, etyp)
			if err != nil {
				return errorx.Wrap(err, "walk map", datax.M{"key": key})
			}
		case *BasicType:
			visitor.walkBasicType()
		case *EnumType:
			err := visitor.walkEnumType(iVal, iTargetValue, etyp)
			if err != nil {
				return errorx.Wrap(err, "walk map", datax.M{"key": key})
			}
		case *AnyType:
			err := visitor.walkAnyType(iVal, iTargetValue, etyp)
			if err != nil {
				return errorx.Wrap(err, "walk any", datax.M{"key": key})
			}
		default:
			return errorx.New("not support type: " + etyp.String())

		}
	}

	return nil
}

func (visitor *Visitor) walkMapType(iObj, iTargetObj interface{}, ftyp *MapType) error {

	if iObj == nil {
		return nil
	}
	mval, ok := iObj.(map[string]interface{})
	if ok == false {
		if iObj == nil {
			return nil
		}
		return errorx.New("value not map[string]interface{}")
	}
	var targetObj map[string]interface{}
	if visitor.hasTarget {
		targetObj = iTargetObj.(map[string]interface{})
	}
	ctx := visitor.ctx
	for key, iVal := range mval {
		var iTargetValue interface{}
		if visitor.hasTarget {
			iTargetValue = targetObj[key]
			if visitor.needCreateTarget(ftyp.Value) {
				if iTargetValue == nil {
					iTargetValue = datax.M{}
					targetObj[key] = iTargetValue
				}
			}
			ctx.CurrentTargetParent = targetObj
			ctx.CurrentTarget = iTargetValue
		}

		switch etyp := ftyp.Value.(type) {
		case *ClassType:
			err := visitor.walkClassType(iVal, iTargetValue, etyp)
			if err != nil {
				return errorx.Wrap(err, "walk map", datax.M{"key": key})
			}
		case *BasicType:
			visitor.walkBasicType()
		case *EnumType:
			err := visitor.walkEnumType(iVal, iTargetValue, etyp)
			if err != nil {
				return errorx.Wrap(err, "walk map", datax.M{"key": key})
			}
		case *AnyType:
			err := visitor.walkAnyType(iVal, iTargetValue, etyp)
			if err != nil {
				return errorx.Wrap(err, "walk any", datax.M{"key": key})
			}
		default:
			return errorx.New("not support type: " + etyp.String())

		}
	}

	return nil
}
func (visitor *Visitor) walkAnyType(ival, iTargetObj interface{}, ftyp *AnyType) error {
	if ival == nil {
		return nil
	}
	mval, ok := ival.(datax.M)
	if !ok {
		return nil
	}
	if mval == nil {
		return nil
	}

	_type := mapx.String(mval, "_type")
	if _type == "" {
		return nil
	}

	typDecl := GetDeclByKey(_type)
	if typDecl == nil {
		return errorx.New("decl not found", datax.M{"_type": _type})
	}

	var targetObj map[string]interface{}
	if visitor.hasTarget {
		targetObj = iTargetObj.(map[string]interface{})
		targetObj["_type"] = _type
	}

	return visitor.walkObject(mval, targetObj, typDecl.(*ClassDecl))
}
func (visitor *Visitor) walkEnumType(iVal, iTarget interface{}, ftyp *EnumType) error {
	typDecl := GetDeclByKey(ftyp.Decl)
	if typDecl == nil {
		return errorx.New("decl not found", datax.M{"enum": ftyp.Decl})
	}
	enumDecl := typDecl.(*EnumDecl)

	ctx := visitor.ctx
	ctx.CurrentDecl = enumDecl
	visitor.handler(ctx)
	ctx.CurrentDecl = nil
	return nil
}
func (visitor *Visitor) walkBasicType() {
	visitor.handler(visitor.ctx)
}

func (Visitor *Visitor) needCreateTarget(iType IType) bool {
	switch iType.(type) {
	case *ClassType:
		return true
	case *ArrayType:
		return true
	case *MapType:
		return true
	case *AnyType:
		return true
	}

	return false

}
